{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 波士顿房价预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch  \n",
    "import torch.nn as nn  \n",
    "import torch.optim as optim  \n",
    "from torch.utils.data import DataLoader, TensorDataset  \n",
    "import pandas as pd\n",
    "from tqdm.notebook import tqdm\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "1\n"
     ]
    }
   ],
   "source": [
    "print(torch.cuda.is_available())\n",
    "print(torch.cuda.device_count())\n",
    "device = torch.device('cuda' if torch.cuda.is_available else 'cpu')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "da26d1b6e8b2406d85b13fe307ed9355",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "  0%|          | 0/500 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch [100/500], Loss: 0.0452\n",
      "Epoch [200/500], Loss: 0.0374\n",
      "Epoch [300/500], Loss: 0.0019\n",
      "Epoch [400/500], Loss: 0.0020\n",
      "Epoch [500/500], Loss: 0.0008\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAkAAAAHHCAYAAABXx+fLAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAABFB0lEQVR4nO3de3wU1eH///despv7hmtCIBAVNCIQlEsMItCSGi2torZG6keQjz/9oGDhi6UVraCtNtqKH6yg1lpF/WjBS0FUpGIErIoiAVRAQRFIBHIDcie33fn9kWTDQkAuycwmeT0fj3ns7syZmTMjbd6Pc86csRmGYQgAAKADsVtdAQAAALMRgAAAQIdDAAIAAB0OAQgAAHQ4BCAAANDhEIAAAECHQwACAAAdDgEIAAB0OAQgAADQ4RCAAABAh0MAAnDGFi1aJJvNpg0bNlhdlZOyefNm/dd//ZcSEhLkdrvVuXNnpaWl6bnnnpPX67W6egBM4LS6AgBgpmeeeUZTpkxRbGysbrzxRvXr109lZWXKysrSzTffrP379+vuu++2upoAWhkBCECH8cknn2jKlClKTU3VihUrFBUV5d82Y8YMbdiwQVu2bGmRc1VUVCgiIqJFjgWg5dEFBsA0mzZt0hVXXKHo6GhFRkZq7Nix+uSTTwLK1NbW6v7771e/fv0UGhqqLl26aOTIkVq1apW/TF5eniZPnqxevXrJ7XarR48euuqqq7R79+4Tnv/++++XzWbTSy+9FBB+Gg0dOlQ33XSTJGnNmjWy2Wxas2ZNQJndu3fLZrNp0aJF/nU33XSTIiMjtXPnTv30pz9VVFSUbrjhBk2bNk2RkZGqrKw85lwTJkxQXFxcQJfbO++8o0svvVQRERGKiorSuHHjtHXr1hNeE4DTQwACYIqtW7fq0ksv1eeff67f/va3uvfee7Vr1y6NGTNGn376qb/cfffdp/vvv18/+tGPtGDBAt1zzz3q3bu3Nm7c6C9z7bXXaunSpZo8ebKeeOIJ/frXv1ZZWZlycnKOe/7KykplZWVp1KhR6t27d4tfX11dndLT09W9e3c98sgjuvbaa5WRkaGKigq9/fbbx9TlzTff1C9+8Qs5HA5J0osvvqhx48YpMjJSDz/8sO69915t27ZNI0eO/MFgB+A0GABwhp577jlDkvHZZ58dt8z48eMNl8tl7Ny5079u3759RlRUlDFq1Cj/uuTkZGPcuHHHPc6hQ4cMScZf/vKXU6rj559/bkgypk+fflLlV69ebUgyVq9eHbB+165dhiTjueee86+bNGmSIcm46667Asr6fD6jZ8+exrXXXhuw/pVXXjEkGR988IFhGIZRVlZmxMTEGLfccktAuby8PMPj8RyzHsCZowUIQKvzer169913NX78eJ199tn+9T169NCvfvUrffjhhyotLZUkxcTEaOvWrfrmm2+aPVZYWJhcLpfWrFmjQ4cOnXQdGo/fXNdXS7ntttsCfttsNv3yl7/UihUrVF5e7l+/ZMkS9ezZUyNHjpQkrVq1SsXFxZowYYKKior8i8PhUEpKilavXt1qdQY6KgIQgFZXWFioyspKnXfeecdsO//88+Xz+ZSbmytJ+sMf/qDi4mKde+65GjhwoGbNmqUvvvjCX97tduvhhx/WO++8o9jYWI0aNUp//vOflZeXd8I6REdHS5LKyspa8MqaOJ1O9erV65j1GRkZOnz4sJYvXy5JKi8v14oVK/TLX/5SNptNkvxh78c//rG6desWsLz77rsqKCholToDHRkBCEBQGTVqlHbu3Klnn31WAwYM0DPPPKOLLrpIzzzzjL/MjBkztGPHDmVmZio0NFT33nuvzj//fG3atOm4x+3bt6+cTqe+/PLLk6pHYzg52vHmCXK73bLbj/2/1IsvvliJiYl65ZVXJElvvvmmDh8+rIyMDH8Zn88nqX4c0KpVq45Z3njjjZOqM4CTRwAC0Oq6deum8PBwbd++/ZhtX3/9tex2uxISEvzrOnfurMmTJ+uf//yncnNzNWjQIN13330B+51zzjm688479e6772rLli2qqanRvHnzjluH8PBw/fjHP9YHH3zgb206kU6dOkmSiouLA9bv2bPnB/c92nXXXaeVK1eqtLRUS5YsUWJioi6++OKAa5Gk7t27Ky0t7ZhlzJgxp3xOACdGAALQ6hwOhy677DK98cYbAU805efn6+WXX9bIkSP9XVQHDhwI2DcyMlJ9+/ZVdXW1pPonqKqqqgLKnHPOOYqKivKXOZ65c+fKMAzdeOONAWNyGmVnZ+v555+XJPXp00cOh0MffPBBQJknnnji5C76CBkZGaqurtbzzz+vlStX6rrrrgvYnp6erujoaP3pT39SbW3tMfsXFhae8jkBnBgTIQJoMc8++6xW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KVOmGL179zbef/99Y8OGDUZqaqqRmprq3974OOtll11mbN682Vi5cqXRrVs3Hhk+yl133WWsXbvW2LVrl/HFF18Yd911l2Gz2Yx3333XMAzuc2s68ikww+Bet4Q777zTWLNmjbFr1y7jo48+MtLS0oyuXbsaBQUFhmEE5z0mAJnk8ccfN3r37m24XC5j+PDhxieffGJ1ldqU1atXG5KOWSZNmmQYRv2j8Pfee68RGxtruN1uY+zYscb27dsDjnHgwAFjwoQJRmRkpBEdHW1MnjzZKCsrs+Bqgldz91iS8dxzz/nLHD582Lj99tuNTp06GeHh4cbVV19t7N+/P+A4u3fvNq644gojLCzM6Nq1q3HnnXcatbW1Jl9NcPvv//5vo0+fPobL5TK6detmjB071h9+DIP73JqODkDc6zOXkZFh9OjRw3C5XEbPnj2NjIwM49tvv/VvD8Z7bDMMw2idtiUAAIDgxBggAADQ4RCAAABAh0MAAgAAHQ4BCAAAdDgEIAAA0OEQgAAAQIdDAAIAAB0OAQgAmpGYmKj58+dbXQ0ArYQABMByN910k8aPHy9JGjNmjGbMmGHauRctWqSYmJhj1n/22We69dZbTasHAHM5ra4AALSGmpoauVyu096/W7duLVgbAMGGFiAAQeOmm27S2rVr9dhjj8lms8lms2n37t2SpC1btuiKK65QZGSkYmNjdeONN6qoqMi/75gxYzRt2jTNmDFDXbt2VXp6uiTp0Ucf1cCBAxUREaGEhATdfvvtKi8vlyStWbNGkydPVklJif989913n6Rju8BycnJ01VVXKTIyUtHR0bruuuuUn5/v337fffdp8ODBevHFF5WYmCiPx6Prr79eZWVlrXvTAJwWAhCAoPHYY48pNTVVt9xyi/bv36/9+/crISFBxcXF+vGPf6wLL7xQGzZs0MqVK5Wfn6/rrrsuYP/nn39eLpdLH330kZ566ilJkt1u11//+ldt3bpVzz//vN5//3399re/lSSNGDFC8+fPV3R0tP98v/nNb46pl8/n01VXXaWDBw9q7dq1WrVqlb777jtlZGQElNu5c6eWLVumt956S2+99ZbWrl2rhx56qJXuFoAzQRcYgKDh8XjkcrkUHh6uuLg4//oFCxbowgsv1J/+9Cf/umeffVYJCQnasWOHzj33XElSv3799Oc//zngmEeOJ0pMTNQDDzygKVOm6IknnpDL5ZLH45HNZgs439GysrL05ZdfateuXUpISJAkvfDCC7rgggv02WefadiwYZLqg9KiRYsUFRUlSbrxxhuVlZWlBx988MxuDIAWRwsQgKD3+eefa/Xq1YqMjPQvSUlJkupbXRoNGTLkmH3fe+89jR07Vj179lRUVJRuvPFGHThwQJWVlSd9/q+++koJCQn+8CNJ/fv3V0xMjL766iv/usTERH/4kaQePXqooKDglK4VgDloAQIQ9MrLy/Xzn/9cDz/88DHbevTo4f8eERERsG337t362c9+pttuu00PPvigOnfurA8//FA333yzampqFB4e3qL1DAkJCfhts9nk8/la9BwAWgYBCEBQcblc8nq9Aesuuugivf7660pMTJTTefL/t5WdnS2fz6d58+bJbq9v8H7llVd+8HxHO//885Wbm6vc3Fx/K9C2bdtUXFys/v37n3R9AAQPusAABJXExER9+umn2r17t4qKiuTz+TR16lQdPHhQEyZM0GeffaadO3fq3//+tyZPnnzC8NK3b1/V1tbq8ccf13fffacXX3zRPzj6yPOVl5crKytLRUVFzXaNpaWlaeDAgbrhhhu0ceNGrV+/XhMnTtTo0aM1dOjQFr8HAFofAQhAUPnNb34jh8Oh/v37q1u3bsrJyVF8fLw++ugjeb1eXXbZZRo4cKBmzJihmJgYf8tOc5KTk/Xoo4/q4Ycf1oABA/TSSy8pMzMzoMyIESM0ZcoUZWRkqFu3bscMopbqu7LeeOMNderUSaNGjVJaWprOPvtsLVmypMWvH4A5bIZhGFZXAgAAwEy0AAEAgA6HAAQAADocAhAAAOhwCEAAAKDDIQABAIAOhwAEAAA6HAIQAADocAhAAACgwyEAAQCADocABAAAOhwCEAAA6HAIQAAAoMP5/wFnk985R4gUUwAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# 定义MLP模型  \n",
    "class MLP(nn.Module):  \n",
    "    def __init__(self, input_size, hidden_size1, hidden_size2, output_size):  \n",
    "        super(MLP, self).__init__()  \n",
    "        self.fc1 = nn.Linear(input_size, hidden_size1)\n",
    "        self.relu6 = nn.ReLU6()\n",
    "        self.fc2 = nn.Linear(hidden_size1,hidden_size2)\n",
    "        self.relu6 = nn.ReLU6()\n",
    "        self.fc3 = nn.Linear(hidden_size2, output_size) \n",
    "        # nn.init.xavier_uniform_(self.fc1.weight)\n",
    "        # nn.init.xavier_uniform_(self.fc1.bias)\n",
    "        # nn.init.xavier_uniform_(self.fc2.weight)\n",
    "        # nn.init.xavier_uniform_(self.fc2.bias)\n",
    "        # nn.init.xavier_uniform_(self.fc3.weight)\n",
    "        # nn.init.xavier_uniform_(self.fc3.bias)\n",
    "\n",
    "    def forward(self, x):  \n",
    "        x = self.fc1(x) \n",
    "        x = self.relu6(x)  \n",
    "        x = self.fc2(x)\n",
    "        x = self.relu6(x)  \n",
    "        x = self.fc3(x)  \n",
    "        return x  \n",
    "  \n",
    "def normalization(data):\n",
    "    # 计算数据集的最大值，最小值，平均值\n",
    "    maximums, minimums, avgs = data.max(axis=0), data.min(axis=0), \\\n",
    "        data.sum(axis=0) / data.shape[0]\n",
    "    epsilon = 1e-8\n",
    "    # 对数据进行归一化处理\n",
    "    for i in range(data.shape[1]):\n",
    "        data[:, i] = (data[:, i] - avgs[i]) / \\\n",
    "            (maximums[i] - minimums[i] + epsilon)\n",
    "    return data, maximums, minimums, avgs\n",
    "  \n",
    "# 加载数据 读取CSV文件  \n",
    "df = pd.read_csv('D:/python-learning-gitee/python-learning/机器学习/boston.csv')  \n",
    "\n",
    "#正则化初始数据\n",
    "data, maximums, minimums, avgs=normalization(df.values)\n",
    "\n",
    "# 将DataFrame转换为Tensor类型\n",
    "data = torch.from_numpy(data).to(torch.float32)\n",
    "\n",
    "#前400组数据作为训练集 剩下的100组数据作为测试集 利用数组切片将原始数据划分\n",
    "train_data = data[0:400,...][...,0:13]\n",
    "train_labels = data[0:400,...][...,13:14]\n",
    "test_data = data[400:500,...][...,0:13]\n",
    "test_labels = data[400:500,...][...,13:14]\n",
    "\n",
    "# 创建数据加载器  \n",
    "train_dataset = TensorDataset(train_data, train_labels)  \n",
    "train_loader = DataLoader(dataset=train_dataset, batch_size=4, shuffle=True)\n",
    "\n",
    "# 初始化模型和优化器  \n",
    "model = MLP(input_size=13,hidden_size1=5,hidden_size2=2,output_size=1)\n",
    "criterion = nn.MSELoss()  \n",
    "optimizer = optim.Adam(model.parameters(), lr=0.001,betas=(0.9,0.999),eps=1e-8,weight_decay=0,amsgrad=False)  \n",
    "# optimizer = optim.RMSprop(model.parameters(), lr=0.001)\n",
    "\n",
    "num_epochs=500\n",
    "# 初始化最小损失和最佳模型参数  \n",
    "min_loss = float('inf')  \n",
    "best_model_state = None\n",
    "\n",
    "# 初始化一个列表来保存损失值  \n",
    "losses = [] \n",
    "batch_losses = []\n",
    "# 训练模型  \n",
    "for epoch in tqdm(range(num_epochs),colour='blue'):  \n",
    "    for i, (inputs, labels) in enumerate(train_loader):  \n",
    "        # 前向传播  \n",
    "        outputs = model.forward(inputs)  \n",
    "        loss = criterion(outputs, labels)  \n",
    "        batch_losses.append(loss.item()) \n",
    "        # 反向传播和优化  \n",
    "        optimizer.zero_grad()  \n",
    "        loss.backward()  \n",
    "        optimizer.step()\n",
    "\n",
    "    # 将平均损失值添加到列表中  \n",
    "    losses.append(sum(batch_losses) / len(batch_losses))\n",
    "\n",
    "    # 更新最小损失和最佳模型参数\n",
    "    if loss.item() < min_loss:  \n",
    "        min_loss = loss.item()  \n",
    "        best_model_state = model.state_dict()\n",
    "            \n",
    "    if (epoch+1) % 100 == 0:  \n",
    "        print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')  \n",
    "\n",
    "# 训练结束后，使用best_model_state来加载最佳模型参数  \n",
    "model.load_state_dict(best_model_state)\n",
    "\n",
    "\n",
    "# 使用matplotlib绘制损失曲线  \n",
    "plt.plot(np.arange(1,len(losses)+1),losses)  \n",
    "plt.xlabel('Iteration')  \n",
    "plt.ylabel('Loss')  \n",
    "plt.title('Loss Curve')  \n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss 34.267669677734375\n"
     ]
    }
   ],
   "source": [
    "model.load_state_dict(best_model_state) \n",
    "predict = model.forward(test_data)  \n",
    "\n",
    "predict= predict*(maximums[13]-minimums[13]) + avgs[13]\n",
    "test_labels = test_labels*(maximums[13]-minimums[13]) + avgs[13]\n",
    "print(f'loss {torch.sum((predict-test_labels)**2) / len(test_labels)}')\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 数字识别问题"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "d:\\Python\\Lib\\site-packages\\torch\\nn\\modules\\module.py:1518: UserWarning: Implicit dimension choice for log_softmax has been deprecated. Change the call to include dim=X as an argument.\n",
      "  return self._call_impl(*args, **kwargs)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "inital accuracy:0.0\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "461958b28dfd4d919c6461dee37f5fcf",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "  0%|          | 0/10 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch : 1, accuracy : 92.83%\n",
      "epoch : 2, accuracy : 94.70%\n",
      "epoch : 3, accuracy : 95.61%\n",
      "epoch : 4, accuracy : 96.45%\n",
      "epoch : 5, accuracy : 96.80%\n",
      "epoch : 6, accuracy : 97.04%\n",
      "epoch : 7, accuracy : 97.09%\n",
      "epoch : 8, accuracy : 97.44%\n",
      "epoch : 9, accuracy : 97.66%\n",
      "epoch : 10, accuracy : 97.64%\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.utils.data import DataLoader\n",
    "from torchvision import transforms\n",
    "from torchvision.datasets import MNIST\n",
    "from tqdm.notebook import tqdm\n",
    "\n",
    "class Net(torch.nn.Module):\n",
    "    def __init__(self, input_size, hidden_size1, hidden_size2, output_size):\n",
    "        super(Net, self).__init__()\n",
    "        self.fc1 = torch.nn.Linear(input_size, hidden_size1)\n",
    "        self.relu6 = torch.nn.ReLU6()\n",
    "        self.fc2 = torch.nn.Linear(hidden_size1, hidden_size2)\n",
    "        self.log_softmax = torch.nn.LogSoftmax()\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.fc1(x)\n",
    "        x = self.relu6(x)\n",
    "        x = self.fc2(x)\n",
    "        x = self.log_softmax(x)\n",
    "        return x\n",
    "\n",
    "\n",
    "to_tensor = transforms.Compose([transforms.ToTensor()])\n",
    "\n",
    "# 加载MNIST数据集\n",
    "train_set = MNIST(\"\", train=True, transform=to_tensor, download=True)\n",
    "test_set = MNIST(\"\", train=False, transform=to_tensor, download=True)\n",
    "\n",
    "train_loader = DataLoader(train_set, batch_size=128, shuffle=True)\n",
    "test_loader = DataLoader(test_set, batch_size=128, shuffle=True)\n",
    "\n",
    "\n",
    "def evaluate(test_data, net):\n",
    "    n_correct = 0\n",
    "    n_total = 0\n",
    "    with torch.no_grad():\n",
    "        for (x, y) in test_data:\n",
    "            outputs = net.forward(x.view(-1, 28*28))\n",
    "            for i, output in enumerate(outputs):\n",
    "                if torch.argmax(output) == y[i]:\n",
    "                    n_correct += 1\n",
    "                n_total += 1\n",
    "    return n_correct / n_total\n",
    "\n",
    "\n",
    "net = Net(input_size=28*28, hidden_size1=128, hidden_size2=64, output_size=10)\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "print(f'inital accuracy:{evaluate(test_loader, net)}')\n",
    "optimizer = torch.optim.Adam(net.parameters(), lr=0.001)\n",
    "epochs = 10\n",
    "for epoch in tqdm(range(epochs)):\n",
    "    for (x, y) in train_loader:\n",
    "        net.zero_grad()\n",
    "        output = net.forward(x.view(-1, 28*28))\n",
    "        loss = criterion(output, y)\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "    print(f'epoch : {epoch+1}, accuracy : {evaluate(test_loader, net)*100:.2f}%')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
